/**
 * 显示在中央区域, 占着标题位置, 用显著的动画效果告诉大家计时器开始.
 * 还有玩家的计时结束条, 放在这个 canvas 是因为它能显示到 windows 挖空区域的上方.
 */

import { Sprite, ZRenderComp } from '../../../common/component/zrComp';

import layout from './layout4';
import events from './events';
import { figures } from '../../../common/paths/fonts/timerFigures0';
import { cubicOut, hold, quinticIn, quinticOut } from '../../../common/animation';

const pathKai = "M605,205.7L577.6,361h66.2l-46,68l-32.1 -0.6l-36.7,208l-106.6,47.8l45.1 -255.8H249.3l-37.2,210.9l-105.7,42.8l44.7 -253.7H84.4l12.1 -66.8h66.5L190.4,206h-63.9l12.7 -68.8h539.9L635.1,206L605,205.7z"
+ "M506.8,206H288.5l-27.4,155.3h218.3L506.8,206z"
const pathShi = "M237,194.3h33.1l10.7 -60.6h86.7l-10.7,60.6l44.5,0.3l-39.7,69.1l-16.9 -0.3l-40.1,228.8l-60.3,69.4L307.8,675H206.2l-24.4 -41.3l-36,41.3H43l96.5 -113.4l-39.8 -68.3l40.3 -230h-24l12.2 -69.1h24l10.6 -60.1h85L237,194.3z"
+ "M225.1,263.4l-35.3,200.4L203,487l19.6 -23.1l35.3 -200.4H225.1z"
+ "M550.2,138l-99.2,184h89.6l-0.7 -102.5h87.3l-0.7,169.9h-86.1l0.5 -52.4l-30,52.4H357l15.1 -85.5L460.2,138H550.2z"
+ "M610.9,421.9l-32.5,184.6l-91,67.4H310.7l44.4 -252H610.9z"
+ "M516,487.8h-90.5l-21.1,119.8h90.5L516,487.8z"

/**
 * 计时开始的动画:
 * 字体大概高度 48, 颜色亮蓝色;
 * 
 * zlevel=60, 静态
 */
class Tip extends ZRenderComp {
	// 开始部分的初始参数
	/** '开' 初始位置 */ cKaiOriX = 0; cKaiOriY = 0;
	/** '始' 初始位置 */ cShiOriX = 0; cShiOriY = 0;

	/** @type {TipRadial[]} 黄色射线 */ spRadials = [];

	constructor(args) {
		super(args.parent);
		const l = layout.tip;
		this.createNewZRenderInstance(l.x, l.y, l.width, l.height, 'tips');

		this.spRecords = layout.recordTags.map(rl => new RecordTag({
			parent: this, ...rl
		}));

		this.on(events.TIMER_START, this.onStart.bind(this));
	}

	init() {
		const {contentX:cx, contentY:cy} = layout.tip;

		this.spKai = this.createPathFromString(pathKai)
			.fill(layout.colors.strokeNormal)
			.shadow(layout.colors.strokeNormal, 12)
			.z(60, 305)
			.build();
		let rect = this.spKai.getBoundingRect();
		const textScale = 54 / rect.height;
		this.cKaiOriX = cx - 100 - (rect.width + rect.x) * textScale;
		this.cKaiOriY = cy - (rect.height / 2 + rect.y) * textScale;
		this.spKai.scaleTo(textScale);
		this.spKai.moveTo(this.cKaiOriX, this.cKaiOriY);
		this.spKai.hide();

		this.spShi = this.createPathFromString(pathShi)
			.fill(layout.colors.strokeNormal)
			.shadow(layout.colors.strokeNormal, 12)
			.z(60, 305)
			.build();
		rect = this.spShi.getBoundingRect();
		this.cShiOriX = cx + 100 - rect.x * textScale;
		this.cShiOriY = cy - (rect.height / 2 + rect.y) * textScale;
		this.spShi.scaleTo(textScale);
		this.spShi.moveTo(this.cShiOriX, this.cShiOriY);
		this.spShi.hide();
	}

	onStart() {
		this.clearAnimations();

		const {spKai, spShi} = this;
		spKai.moveTo(this.cKaiOriX, this.cKaiOriY);
		spKai.attr({ style : {opacity: 0}});
		spKai.show();
		spShi.moveTo(this.cShiOriX, this.cShiOriY);
		spShi.attr({ style : {opacity: 0}});
		spShi.show();

		this.createAnimation('sp.x')
			.whenUpdated(p => {
				spKai.moveTo(this.cKaiOriX + p.value, this.cKaiOriY);
				spShi.moveTo(this.cShiOriX - p.value, this.cShiOriY);
			})
			.update()
			.when(100, 0)
			.when(600, 100).easing(quinticIn)
			.when(900, 95).easing(quinticOut)
			.commit();

		this.createAnimation('sp.opacity')
			.whenUpdated(p => {
				spKai.attr({style : {opacity : p.value}});
				spShi.attr({style : {opacity : p.value}});
			})
			.update()
			.when(0, 0)
			.when(300, 1)
			.when(3000, 1).easing(hold)
			.when(3300, 0)
			.when(4000, 0).easing(hold).callback(() => { spKai.hide(); spShi.hide(); })
			.commit();

		for (let i = 0; i < 16; i++) {
			this.spRadials[i] = new TipRadial({ parent: this });
		}
	}
}

class TipRadial extends ZRenderComp {
	constructor(args) {
		super(args.parent);
		this.useParentZRenderInstance(layout.tip.contentX, layout.tip.contentY);
		/** @type {Sprite[]} */ this.sprites = [];
	}

	init() {
		const {floor, random, min} = Math;

		// 从 0-7 方向是正上方 -> 顺时针 8 个方向
		let direction = floor(random() * 4) * 2 + 1; // 可能 1, 3, 5, 7
		/** @type {number} */
		let firstDirection = direction;
		let remain; // 3 条折线, 每条折线长度 100
		let currentXY = [0, 0];
		let remainCount = 2 + floor(random() * 4);
		for (; remainCount > 0; remainCount--) {
			let points = [[...currentXY]];
			remain = 70 + floor(random() * 60);
			while (remain > 0) {
				let w = min(floor(random() * 40 + 20), remain);
				remain -= w;
				switch (direction) {
					case 0: currentXY[1] -= w; break;
					case 1: currentXY[0] += 0.7 * w; currentXY[1] -= 0.7 * w; break;
					case 2: currentXY[0] += w; break;
					case 3: currentXY[0] += 0.7 * w; currentXY[1] += 0.7 * w; break;
					case 4: currentXY[1] += w; break;
					case 5: currentXY[0] -= 0.7 * w; currentXY[1] += 0.7 * w; break;
					case 6: currentXY[0] -= w; break;
					case 7: currentXY[0] -= 0.7 * w; currentXY[1] -= 0.7 * w; break;
				}
				points.push([...currentXY]);

				let d = random() * 10;
				direction = (d < 3) ? firstDirection : (d < 5) ? direction : (d < 7.5) ? (direction + 1) % 8 : (direction - 1) % 8;
			}

			let sp = this.createPolyline(...points)
				.stroke(layout.colors.yellowNormal, 3)
				.shadow(layout.colors.yellowNormal, 12)
				.opacity(0)
				.position(0, 0)
				.z(60, random() > 0.7 ? 310 : 304)
				.build();
			this.sprites.push(sp);
		}

		// 动画
		this.sprites.forEach((sp, index) => {
			let timeOffset = index * 40;
			let el = this.createAnimation('opacity-' + index, 0)
				.whenUpdated(p => sp.opacity(p.value))
				.update();

			el.when(600 + timeOffset, 0)
				.when(650 + timeOffset, 1)
				.when(1450 + timeOffset, 1, hold)
				.when(1500 + timeOffset, 0);

			let lastOpacity = 0;
			for (let i = 0; i < 5; i++) {
				let thisOpacity = random() > 0.7 ? 1 : 0;
				el.when(1500 + timeOffset + i*125 + 67, (lastOpacity + thisOpacity) / 2, hold)
					.when(1500 + timeOffset + i*125 + 125, thisOpacity, hold)
				lastOpacity = thisOpacity;
			}
			el.when(2300 + timeOffset, 0, hold, () => {
				if (index == this.sprites.length - 1) {
					this.destroy();
				}
			});
			el.commit();
		});
	}
}

/**
 * 数值显示的参数
 */
const figureStatic = {
	firstWidth : 16, // ms 部分的大小
	eachWidth : 24,
	firstHeight : 24, // ms 部分的大小
	eachHeight : 36,
	firstScale : 0.04,
	eachScale : 0.06,

	// 摆放位置速查 (整个文本右下角位置	和	对应数字左下角的 x 之差)
	figureX : [
		14, 28, 42, // ms 级
		75, 94, // s 级
		125, 144, // m 级
		175, // h 级
	],
	pointX : [60, 110, 160],
};

/**
 * 最后显示成绩的部分
 */
class RecordTag extends ZRenderComp {
	// 当前显示数值 (单位 ms), 0 为不显示
	score = 0;

	/** @type {Sprite[]} 数值 */ spFigures = [];
	/** @type {Sprite[]} 小数点 */ spPoints = [];

	// 动画相关参数
	// startAniTime = 0; // 0 表示无动画, 否则就是启动动画的 start timestamp
	// figureAni = false;
	// recordShowed = false; // 当前记录的板子是否在显示

	constructor(args) {
		super(args.parent);
		this.useParentZRenderInstance(args.x, args.y);

		this.width = args.width;
		this.height = args.height;
		this.direction = args.direction;
		this.playerNo = args.playerNo;

		this.on(events.PLAYER_FINISH, this.showTag.bind(this));
		this.on(events.PLAYER_START, this.hideTag.bind(this));
		this.on(events.PLAYER_STOP, this.hideTag.bind(this));
		this.on(events.PLAYER_RESET, this.hideTag.bind(this));
	}

	init() {
		const {height:h, width:w, direction} = this;

		const delta = (direction) ? 1 : -1; // 左 false 右 true
		const o = delta * h/2;
		const ww = delta * w;
		this.spBg = this.createPolygon([0, 0], [-o, h/2], [-o+ww, h/2], [ww, 0], [-o+ww, -h/2], [-o, -h/2])
			.fill(layout.colors.nameTagBgL3)
			.opacity(0.6)
			.z(60, 1)
			.build();
		this.spBg.hide();
		this.spStroke = this.createPolygon([0, 0], [-o, h/2], [-o+ww, h/2], [ww, 0], [-o+ww, -h/2], [-o, -h/2])
			.stroke(layout.colors.nameTagBgL2)
			.fill('transparent')
			.opacity(0.7)
			.z(60, 2)
			.build();
		this.spStroke.hide();

		const hw = delta * 10;
		this.spHead = this.createPolygon([0, 0], [-o, h/2], [-o+hw, h/2], [hw, 0], [-o+hw, -h/2], [-o, -h/2])
			.fill(layout.colors.nameTagBgL2)
			.opacity(0.9)
			.z(60, 3)
			.build();
		this.spHead.hide();
	}

	showTag(_, param) {
		if (param.playerNo != this.playerNo) {
			return;
		}
		// 如果玩家弃权了, 不显示
		if (this.root.scoreBoard.findPlayer(this.playerNo).isWaivered()) {
			return;
		}

		const delta = (this.direction) ? 1 : -1; // 左 false 右 true
		const o = delta * this.height/2;
		const w = delta * this.width;

		const elWidth = this.createAnimation('width', 0)
			.whenUpdated(p => {
				const spBgPoints = this.spBg.sp.shape.points;
				spBgPoints[2][0] = -o + p.value;
				spBgPoints[3][0] = p.value;
				spBgPoints[4][0] = -o + p.value;
				this.spBg.sp.dirty();
				const spStrokePoints = this.spStroke.sp.shape.points;
				spStrokePoints[2][0] = -o + p.value;
				spStrokePoints[3][0] = p.value;
				spStrokePoints[4][0] = -o + p.value;
				this.spStroke.sp.dirty();
				this.spHead.moveTo(p.value);
			})
			.update()
			.when(450, 0).easing(hold).callback(() => {
				this.spBg.show();
				this.spStroke.show();
				this.spHead.show();
			})
			.when(1000, w).easing(quinticOut)
			.commit();

		this.spBg.hide();
		this.spStroke.hide();
		this.spHead.hide();

		// 成绩
		this.putFigure();
	}

	hideTag(_, param) {
		if (param.playerNo != this.playerNo) {
			return;
		}
		this.spBg.hide();
		this.spStroke.hide();
		this.spHead.hide();

		if (this.spPoints.length > 0) {
			this.spFigures.forEach(f => f.destroy());
			this.spPoints.forEach(f => f.destroy());
			this.spFigures = [];
			this.spPoints = [];
		}
	}

	/**
	 * 数值显示:
	 * 一般数值
	 */
	putFigure() {
		const score = this.root.scoreBoard.findPlayer(this.playerNo).score;

		if (this.spPoints.length > 0) {
			this.spFigures.forEach(f => f.destroy());
			this.spPoints.forEach(f => f.destroy());
			this.spFigures = [];
			this.spPoints = [];
		}

		let textLenInPixel = 0;
		A : {
			// ms
			this.createFigureSprite(score % 10, 0);
			this.createFigureSprite((score % 100 / 10) | 0, 1);
			this.createFigureSprite((score % 1000 / 100) | 0, 2);
			this.createFigureSprite('.', 0, true);
			// s
			const s = (score / 1000) | 0;
			this.createFigureSprite(s % 10, 3);
			if (s < 10) { textLenInPixel = figureStatic.figureX[3]; break A; }
			this.createFigureSprite((s % 60 / 10) | 0, 4);
			if (s < 60) { textLenInPixel = figureStatic.figureX[4]; break A; }
			this.createFigureSprite(':', 1, true);
			// m
			const m = (s / 60) | 0;
			this.createFigureSprite(m % 10, 5);
			if (m < 10) { textLenInPixel = figureStatic.figureX[5]; break A; }
			this.createFigureSprite((m % 60 / 10) | 0, 6);
			if (m < 60) { textLenInPixel = figureStatic.figureX[6]; break A; }
			this.createFigureSprite(':', 2, true);
			// h
			const h = (m / 60) | 0;
			this.createFigureSprite(h % 10, 7);
			textLenInPixel = figureStatic.figureX[7];
		}

		let centerX = (this.direction ? this.width / 2 : -this.width / 2);
		let rightX = centerX + textLenInPixel / 2;
		let y = figureStatic.eachHeight * 0.36;

		const ps = this.spFigures.map((sp, index) => {
			return {
				toY : y - (index <= 2 ? figureStatic.firstHeight : figureStatic.eachHeight),
				xx : rightX - figureStatic.figureX[index],
				sp, name : 'figure-' + index
			};
		});
		this.spPoints.forEach((sp, index) => {
			ps.push({
				toY : y - figureStatic.eachHeight,
				xx : rightX - figureStatic.pointX[index],
				sp, name : 'point-' + index
			});
		});

		ps.forEach(p => {
			p.sp.moveTo(p.xx, p.toY + 20);
			const delay = Math.random() * 300 + 500;
			this.createAnimation(p.name, 0)
				.whenUpdated((args) => {
					p.sp.moveTo(p.xx, p.toY + 20 * (1 - args.value));
					p.sp.attr({ style : {opacity : args.value}});
				})
				.update()
				.when(delay, 0)
				.when(delay + 400, 1).easing(cubicOut)
				.commit();
		});
	}

	createFigureSprite(word, index, isPoint = false) {
		const scale = (index <= 2 && !isPoint) ? figureStatic.firstScale : figureStatic.eachScale;
		const sp = this.createPathFromString(figures[word])
			.fill(layout.colors.yellowNormal)
			.shadow(layout.colors.yellowNormal, 8)
			.opacity(0)
			.scale(scale)
			.z(60, 99)
			.build();
		if (isPoint) {
			this.spPoints[index] = sp;
		} else {
			this.spFigures[index] = sp;
		}
	}
}

export {
	Tip
};
